package	model.code;

import editor.ErrorHandler;
import event.MemberAddedEvent;
import event.MemberRemovedEvent;
import event.SymbolRenameEvent;
import event.SymbolTypeChangeEvent;
import model.code.line.SingleLine;
import model.code.line.LineList;
import model.symbol.DataType;
import model.symbol.Scope;
import model.symbol.StructureMember;
import model.symbol.Symbol;
import observer.ListObserver;
import observer.ObservedList;

/**
 * This is an object to represent a structure.
 *
 * @author Neil Dickson
 */
public class Structure extends CodeFileChunk implements Symbol, DataType, ListObserver<StructureMember> {
	private	ObservedList<StructureMember>	members;

	private Scope							scope;

	private	ObservedList<Rowable>			seeAlso;

	public Structure(String name, String description) {
		super(name, description);
		seeAlso = new ObservedList<Rowable>();
		members = new ObservedList<StructureMember>();
		members.addObserver(this);
		scope = new Scope(Scope.TYPE_STRUCTURE);
	}

	public Scope getScope() {
		return scope;
	}

	public ObservedList<StructureMember> getMembers() {
		return members;
	}

	public ObservedList<Rowable> getSeeAlso() {
		return seeAlso;
	}

	public void addMember(StructureMember member) {
		addMember(member,members.size());
	}
	public MemberAddedEvent addMember(final StructureMember member,int index) {
		return addMember(member,index,true);
	}
	private MemberAddedEvent addMember(final StructureMember member,int index,boolean updateList) {
		if (updateList) {
			members.add(index,member,this);
		}
		member.getName().addObserver(new SingleLine.LineObserver(){
			public void valueChanged(SingleLine s, String oldValue) {
				SymbolRenameEvent event = new SymbolRenameEvent(member,oldValue,s.toString());
				// This is to notify any functions that contain members of this structure, since they don't listen to the structure scope
				// NOTE: Don't need to call symbolChanged() on the structure's scope because it already observes the member names.
				Scope globalScope = getGlobalScope();
				if (globalScope!=null) {
					globalScope.symbolChanged(event);
				}
			}
		});
		member.getType().addObserver(new SingleLine.LineObserver(){
			public void valueChanged(SingleLine s, String oldValue) {
				SymbolTypeChangeEvent event = new SymbolTypeChangeEvent(member,oldValue,s.toString());
				scope.symbolChanged(event);
				// This is to notify any functions that contain members of this structure, since they don't listen to the structure scope
				Scope globalScope = getGlobalScope();
				if (globalScope!=null) {
					globalScope.symbolChanged(event);
				}
			}
		});
		scope.add(member);
		Scope globalScope = getGlobalScope();
		MemberAddedEvent event = new MemberAddedEvent(this,member,index);
		if (globalScope!=null) {
			globalScope.symbolChanged(event);
		}
		return event;
	}
	public MemberRemovedEvent removeMember(int index) {
		return removeMember(members.get(index),index,true);
	}
	private MemberRemovedEvent removeMember(StructureMember member,int index,boolean updateList) {
		if (updateList) {
			members.remove(index,this);
		}
		// FIXME: Remove the StringObservers on the name and type
		scope.remove(member);
		Scope globalScope = getGlobalScope();
		MemberRemovedEvent event = new MemberRemovedEvent(this,member,index);
		if (globalScope!=null) {
			globalScope.symbolChanged(event);
		}
		return event;
	}
	public void moveMember(int oldIndex,int newIndex) {
		moveMember(oldIndex,newIndex,true);
	}
	private void moveMember(int oldIndex,int newIndex,boolean updateList) {
		if (updateList) {
			members.move(oldIndex,newIndex,this);
		}
//		Scope globalScope = getGlobalScope();
//		if (globalScope!=null) {
//			globalScope.symbolChanged(new ParameterMovedEvent(this,oldIndex,newIndex));
//		}
	}

	public void setSeeAlso(String[] seeAlso) {
		this.seeAlso = new ObservedList<Rowable>(seeAlso.length);
		for (String string : seeAlso) {
			this.seeAlso.add(Rowable.SEE_ALSO_DUMMY.copy(this,this.seeAlso.size(),new String[]{string}));
		}
	}

	public String toString() {
		return name.toString();
	}

	public void listElementAdded(ObservedList<StructureMember> s, int index) {
		if (s==members) {
			addMember(members.get(index),index,false);
		}
	}

	public void listElementRemoved(ObservedList<StructureMember> s, int index, StructureMember element) {
		if (s==members) {
			removeMember(element,index,false);
		}
	}

	public void listElementSet(ObservedList<StructureMember> s, int index, StructureMember oldElement) {
		if (s==members) {
			ErrorHandler.showNotImplementedMessage("Sorry, there's an unimplemented function named Structure.listElementSet, and it was called, but it shouldn't have been.  The Structure was being notified that a StructureMember was set.  Hopefully this bug gets fixed in a later version.");
		}
	}

	public void listElementMoved(ObservedList<StructureMember> s, int from, int to) {
		if (s==members) {
			moveMember(from,to,false);
		}
	}

	public int getSize() {
		int total = 0;
		for (StructureMember member : members) {
			total += member.getTotalSize();
		}
		return total;
	}

	public int getMemberOffset(StructureMember member) {
		int total = 0;
		for (StructureMember member2 : members) {
			if (member==member2) {
				return total;
			}
			total += member2.getTotalSize();
		}
		return -1;
	}

	public Structure copy() {
		Structure that = new Structure(getName().toString(),getDescription().toString());
		that.seeAlso.ensureCapacity(seeAlso.size());
		for (Rowable sa : seeAlso) {
			that.seeAlso.add(sa.copy(that,that.seeAlso.size()));
		}
		// add() is used instead of = so that the members are added to the scope properly
		// and that remains an observer of its members
		that.members.ensureCapacity(members.size());
		for (StructureMember member : members) {
			that.members.add(member.copy(that,that.members.size()));
		}
		that.scope.addAll(that.members);
		return that;
	}

	public void nowHaveGlobalScope() {
		scope.setParent(getGlobalScope());
		for (StructureMember member : members) {
			member.nowHaveGlobalScope();
		}
	}

	public LineList getLineList(int locType, int locIndex) {
		int row = locIndex>>>16;
		int col = locIndex&0xFFFF;
		ObservedList<? extends Rowable> table = null;
		if (locType==LOCATION_STRUCTURE_MEMBERS) {
			table = members;
		}
		else if (locType==LOCATION_SEE_ALSO) {
			table = seeAlso;
		}
		else {
			return super.getLineList(locType, locIndex);
		}
		if (table!=null && table.size()>row && table.get(row).getStrings().length>col) {
			return table.get(row).getStrings()[col];
		}
		return null;
	}

	public LineList getNextLineList(LineList list) {
		int locType = list.getLocationType();
		int locIndex = list.getLocationIndex();
		if (locType==LOCATION_NAME) {
			return description;
		}
		if (locType==LOCATION_DESCRIPTION) {
			if (members.size()>0) {
				return getLineList(LOCATION_STRUCTURE_MEMBERS,0);
			}
			if (seeAlso.size()>0) {
				return getLineList(LOCATION_SEE_ALSO,0);
			}
			return null;
		}
		ObservedList<? extends Rowable> table = null;
		int row = locIndex>>>16;
		int col = locIndex&0xFFFF;
		if (locType==LOCATION_STRUCTURE_MEMBERS) {
			table = members;
		}
		if (locType==LOCATION_SEE_ALSO) {
			table = seeAlso;
		}
		if (table!=null) {
			if (col+1<table.get(row).getSize()) {
				return getLineList(locType,locIndex+1);
			}
			if (row+1<table.size()) {
				return getLineList(locType,(row+1)<<16);
			}
			if (locType==LOCATION_STRUCTURE_MEMBERS && seeAlso.size()>0) {
				return getLineList(LOCATION_SEE_ALSO,0);
			}
		}
		return null;
	}
	public LineList getPrevLineList(LineList list) {
		int locType = list.getLocationType();
		int locIndex = list.getLocationIndex();
		if (locType==LOCATION_NAME) {
			return null;
		}
		if (locType==LOCATION_DESCRIPTION) {
			return name;
		}
		ObservedList<? extends Rowable> table = null;
		int row = locIndex>>>16;
		int col = locIndex&0xFFFF;
		if (locType==LOCATION_STRUCTURE_MEMBERS) {
			table = members;
		}
		if (locType==LOCATION_SEE_ALSO) {
			table = seeAlso;
		}
		if (table!=null) {
			if (col-1>=0) {
				return getLineList(locType,locIndex-1);
			}
			if (row-1>=0) {
				return getLineList(locType,(row-1)<<16);
			}
			if (locType==LOCATION_SEE_ALSO && members.size()>0) {
				return members.getLast().getStrings()[members.getLast().getSize()-1];
			}
			return description;
		}
		return null;
	}

	public LineList getLastLineList() {
		if (seeAlso.size()>0) {
			return seeAlso.getLast().getStrings()[seeAlso.getLast().getSize()-1];
		}
		if (members.size()>0) {
			return members.getLast().getStrings()[members.getLast().getSize()-1];
		}
		return description;
	}

}
